package voisfile

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"

	"code.google.com/awwwe-browser/auralvm/conversation"
	"code.google.com/awwwe-browser/auralvm/param"
	"code.google.com/awwwe-browser/auralvm/setting"
	"code.google.com/awwwe-browser/data"
	"code.google.com/awwwe-browser/slurp"
	"code.google.com/awwwe-browser/vois"
)

// Alternative HTTP handler for sending data back to the view.
var NextContinuer func(http.ResponseWriter, *http.Request, string)
// Final alternative HTTP handler for sending data back to the view.
var StopContinuer func(http.ResponseWriter, *http.Request, string)

// CountVOISFiles returns the number of vois files that the user has stored in the file.
func CountVOISFiles() int {
	var names []string
	var err error
	names, err = getVOISFileList()
	if err != nil {
		return 0
	} else {
		return len(names)
	}
}

// URLToVOISFileName builds a vois.xml file for a local vois file name.
func URLToVOISFileName(urls string) (string, error) {
	var location *url.URL
	var err error
	var name string
	var parts []string
	location, err = url.Parse(urls)
	if err == nil {
		parts = strings.Split(location.Host, ":")
		name = strings.Replace(parts[0], ".", "_", -1) + param.LocalVOISFileExt
	} else {
		name = data.EmptyString
	}
	return name, err
}

// HandlerVoisListToOpen lists the vois files of web sites that the user can listen to for the view.
func HandlerVoisListToOpen(w http.ResponseWriter, r *http.Request) {
	ContinuerVoisListToOpen(w, r, data.EmptyString, -1, true)
}

// ContinuerVoisListToOpen continues what HandlerVoisListToOpen started.
func ContinuerVoisListToOpen(w http.ResponseWriter, r *http.Request, intro string, startoptionsat int, continueon bool) {
	var names []string
	var vdata *vois.VOISData
	var arguments []*vois.VOISData
	var name string
	var err error
	var conv conversation.Conversation
	var option conversation.APIOption
	var jsonbb []byte
	var i, length, last int
	arguments = make([]*vois.VOISData, 0, 5)
	names, err = getVOISFileList()
	if err != nil || len(names) == 0 {
		intro = "I was not able to find any of your voice markup files."
		if continueon {
			NextContinuer(w, r, intro)
		} else {
			// internal server error
			StopContinuer(w, r, intro)
		}
		return
	}
	for _, name = range names {
		vdata, err = GetVOISData(name)
		if err == nil {
			arguments = append(arguments, vdata)
		} else {
			break
		}
	}
	if err != nil {
		intro = fmt.Sprintf("I was not able to process your voice markup file named %s.\n Error: %s", name, err.Error())
		if continueon {
			NextContinuer(w, r, intro)
		} else {
			// internal server error
			StopContinuer(w, r, intro)
		}
	}
	// have the vois names of the sites
	// convert to a conversation
	// each site name is an options
	// a "yes" response calls tab/open
	conv = conversation.BuildConversation()
	conv.Type = conversation.ConversationTypeOptions
	conv.Intro = intro
	conv.InitialIndex = startoptionsat
	conv.Header.Append("This tool lets you use your voice markup files to open a web page in a new tab.")
	if setting.BrowserSettings.HelpLevel != setting.SettingHelpLevel3 {
		// a lot of help or a little help.
		// add helpful
		conv.Helpful = " I will iterate through the names of every web site that you have a voice markup file for."
	}
	if setting.BrowserSettings.HelpLevel == setting.SettingHelpLevel1 {
		// a lot of help
		// add more helpful
		conv.MoreHelpful = " After i say the name of a web site, say yes, if you want to open that web site and listen to it."
	}
	conv.Footer = " that is the end of the list of your voice markup files."
	conv.CallHelp.Package = param.Package
	conv.CallHelp.Object = "browser"
	conv.CallHelp.Method = "listtools"
	length = len(arguments)
	last = length - 1
	for i = 0; i < length; i++ {
		vdata = arguments[i]
		option = conversation.BuildAPIOption()
		option.VXMLs.Append(vdata.SiteName)
		option.CallYes.Package = param.Package
		option.CallYes.Object = "tab"
		option.CallYes.Method = "opennew"
		option.CallYes.Arguments[param.ParamTabURL] = vdata.SiteURL
		if i < last {
			// if a tab is not able to open then the vm should continue with listing vois.xml files to open.
			option.CallYes.Arguments[param.ParamSourceObject] = "vois"
			option.CallYes.Arguments[param.ParamSourceMethod] = "listtoopen"
			option.CallYes.Arguments[param.ParamSourceOptionIndex] = fmt.Sprintf("%d", i)
		} else {
			// if a tab is not able to open then the vm should continue with listing the list of vois tools.
			option.CallYes.Arguments[param.ParamSourceObject] = "vois"
			option.CallYes.Arguments[param.ParamSourceMethod] = "listtools"
			option.CallYes.Arguments[param.ParamSourceOptionIndex] = "-1"
		}
		conv.Options = append(conv.Options, option)
	}
	// serialize the conversation
	jsonbb, err = conv.Marshal()
	if err == nil {
		w.Write(jsonbb)
	} else {
		if continueon {
			NextContinuer(w, r, intro)
		} else {
			// internal server error
			StopContinuer(w, r, intro)
		}
	}
}

// GetVOISFileNameURLMap lists all current vois.xml files.
// Responds with a map
// {'name':'value', 'name':'value', ...}
func GetVOISFileNameURLMap() map[string]string {
	var params map[string]string
	var names []string
	var name string
	var vdata *vois.VOISData
	var err error
	params = make(map[string]string)
	names, err = getVOISFileList()
	if err == nil {
		for _, name = range names {
			vdata, err = GetVOISData(name)
			if err == nil {
				params[vdata.SiteName] = vdata.SiteURL
			}
		}
	}
	return params
}

// HandlerVoisSave saves a vois file for the view.
func HandlerVoisSave(w http.ResponseWriter, r *http.Request) {
	var respondwithtext bool
	var values []string
	var markupstr string
	var markupbb []byte
	var vdata *vois.VOISData
	var fname, fpath string
	var err error
	var file *os.File
	var ok bool
	var intro, vdxml, vm string
	// get the markup
	r.ParseForm()
	_, respondwithtext = r.PostForm[param.ParamResponsdWithText]
	if respondwithtext {
		vdxml = "VOIS Markup"
		vm = "VOIS Markup"
	} else {
		vdxml = "voice markup"
		vm = "voice markup"
	}
	values, ok = r.PostForm[param.ParamVoisMarkup]
	if !ok {
		intro = "I was not able to save your %s file because the %s is missing."
		intro = fmt.Sprintf(intro, vdxml, vm)
        NextContinuer(w, r, intro)
		return
	} else if len(values) != 1 {
		intro = "I was not able to save your %s file because the %s got lost."
		intro = fmt.Sprintf(intro, vdxml, vm)
        NextContinuer(w, r, intro)
		return
	}
	markupstr, err = url.QueryUnescape(values[0])
	if !ok {
		intro = "I was not able to save your %s file because there was an error unescaping the %s."
		intro = fmt.Sprintf(intro, vdxml, vm)
        NextContinuer(w, r, intro)
		return
	}
	markupbb, err = slurp.SlurpXMLMarkupBytes([]byte(markupstr))
	if err != nil {
		intro = "I was not able to save your %s file because there was an error reading the file."
		NextContinuer(w, r, fmt.Sprintf(intro, vdxml))
		return
	}
	vdata, err = vois.UnMarshal(markupbb)
	if err != nil {
		intro = "I was not able to save your %s file because there was an error un marshaling the file. %s"
		NextContinuer(w, r, fmt.Sprintf(intro, vdxml, err.Error()))
	}
	fname, err = URLToVOISFileName(vdata.SiteURL)
	if err != nil {
		intro = "I was not able to save your %s file because there was an error naming the file."
		NextContinuer(w, r, fmt.Sprintf(intro, vdxml))
	}
	// save the vois.xml file with its new name
	fpath = path.Join(param.LocalVOISFolderPath, fname)
	file, err = os.Create(fpath)
	if err != nil {
		intro = "I was not able to save your %s file because there was an error saving the file."
	} else {
		file.Write(markupbb)
		file.Close()
		intro = "The %s file has been saved."
	}
	NextContinuer(w, r, fmt.Sprintf(intro, vdxml))
}

// GetVOISData constructs a *vois.VOISData using the bytes from a vois file.
func GetVOISData(path string) (*vois.VOISData, error) {
	var err error
	var bb []byte
	var vd *vois.VOISData
	bb, err = slurp.SlurpXMLMarkupFile(path)
	if err == nil {
		vd, err = vois.UnMarshal(bb)
		if err == io.EOF {
			err = nil
		}
		if err != nil {
			err = errors.New("unmarshal error: " + err.Error())
		}
	} else {
		vd = nil
	}
	return vd, err
}

// GetVOISDataBytes constructs a *vois.VOISData using the bytes of the param voismarkup.
func GetVOISDataBytes(voismarkup []byte) (*vois.VOISData, error) {
	var vd *vois.VOISData
	var err error
	vd, err = vois.UnMarshal(voismarkup)
	if err == io.EOF {
		err = nil
	}
	if err != nil {
		err = errors.New("unmarshal error: " + err.Error())
	}
	return vd, err
}

// GetVOISBytes reads the bytes from a vois markup file using the param siteurl to the the file name.
func GetVOISBytes(siteurl string) ([]byte, error) {
	var fname, fpath string
	var err error
	fname, err = URLToVOISFileName(siteurl)
	if err != nil {
		return data.EmptyBytes, err
	} else {
		fpath = path.Join(param.LocalVOISFolderPath, fname)
		return slurp.SlurpXMLMarkupFile(fpath)
	}
}

// getVOISFileList returns a list of the names of the vois files.
func getVOISFileList() ([]string, error) {
	var ext string
	var path string
	ext = "*" + param.LocalVOISFileExt
	path = filepath.Join(param.LocalVOISFolderPath, ext)
	return filepath.Glob(path)
}
